iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
AI & Data

雲端情人 - AI 愛系列 第 22

Day 22:女友長虫囉:修bugs

  • 分享至 

  • xImage
  •  

連續 22 天了,今天把前幾天踩到的坑一次收尾:
1. 解掉 AsyncWebhookHandler 不存在的錯誤、
2. 讓金價與彩券查詢更穩、
3. 語音 TTS/STT 有雲端與免費備援、
4. 部署在 Render 的健康檢查與環境變數清單。
目標是:線上機器不再紅字爆炸,指令都能回、服務穩穩跑。

前情提要

昨天你在 Render 的日誌看到:
• ImportError: cannot import name 'AsyncWebhookHandler' from 'linebot.v3.webhooks'
• 金價查詢報 AttributeError: 'NoneType' object has no attribute 'find'
• Groq 主模型被下架
• OpenAI API Key 無效導致 TTS/STT 失敗

今天的文章就把 Handler/爬蟲/模型/語音/部署 一次收尾。

  1. Handler:別再用不存在的 AsyncWebhookHandler

症狀

你換到 line-bot-sdk v3(或 3.19.0)後,嘗試匯入:

from linebot.v3.webhooks import AsyncWebhookHandler

結果直接噴:

ImportError: cannot import name 'AsyncWebhookHandler'

原因

目前該版本 沒有 AsyncWebhookHandler。官方穩定做法是用 同步的 WebhookHandler,在 FastAPI 端點裡把 handler.handle(...) 丟到 threadpool,就不會卡 event loop。

修法(精華)

✅ 正確:用同步 Handler

from linebot.v3 import WebhookHandler
handler = WebhookHandler(CHANNEL_SECRET)

✅ FastAPI callback 裡,把處理丟到 threadpool

@router.post("/callback")
async def callback(request: Request):
signature = request.headers.get("X-Line-Signature", "")
body = await request.body()

# 把同步的 handler 放到 thread pool 避免阻塞
await run_in_threadpool(handler.handle, body.decode("utf-8"), signature)
return JSONResponse({"status": "ok"})

這樣既保留 v3 SDK 的優點,也符合 FastAPI 的 async life-cycle,不會再遇到 import 爆炸。

  1. 金價查詢:改抓全頁文字,避開 DOM 改版炸裂

台銀金價頁面常改 class 或 table 結構,與其黏死 DOM,不如解析整頁文字。

關鍵作法
1. BeautifulSoup(...).get_text(" ", strip=True) 把頁面變成一條字串。
2. 用 regex 擷取:
• 掛牌時間
• 本行買進、本行賣出
3. 額外計算買賣價差,順便標註「盤整/偏寬/價差偏大」。

核心程式(摘錄)

BOT_GOLD_URL = "https://rate.bot.com.tw/gold?Lang=zh-TW"
HEADERS = {"User-Agent": "Mozilla/5.0 ..."}

def parse_bot_gold_text(html: str) -> dict:
soup = BeautifulSoup(html, "html.parser")
text = soup.get_text(" ", strip=True)

m_time = re.search(r"掛牌時間[::]\s*(\d{4}/\d{2}/\d{2}\s+\d{2}:\d{2})", text)
m_sell = re.search(r"本行賣出\s*([\d,]+(?:\.\d+)?)", text)
m_buy  = re.search(r"本行買進\s*([\d,]+(?:\.\d+)?)", text)
if not (m_sell and m_buy):
    raise RuntimeError("找不到『本行賣出/本行買進』欄位")

return {
    "listed_at": m_time.group(1) if m_time else None,
    "sell": float(m_sell.group(1).replace(",", "")),
    "buy":  float(m_buy.group(1).replace(",", "")),
}

def get_gold_analysis() -> str:
r = requests.get(BOT_GOLD_URL, headers=HEADERS, timeout=10)
r.raise_for_status()
d = parse_bot_gold_text(r.text)
spread = d["sell"] - d["buy"]
bias = "盤整" if spread <= 30 else ("偏寬" if spread <= 60 else "價差偏大")

return (
    f"**金價快報(台灣銀行)**\n"
    f"- 掛牌時間:{d['listed_at'] or '(頁面未標示)'}\n"
    f"- 本行賣出(1克):**{d['sell']:,.0f} 元**\n"
    f"- 本行買進(1克):**{d['buy']:,.0f} 元**\n"
    f"- 買賣價差:{spread:,.0f} 元({bias})\n"
    f"\n資料來源:{BOT_GOLD_URL}"
)

這招把「找不到 table」的 bug 徹底避開,穩!

  1. 彩券查詢:自訂爬蟲+官網後備解析

需求
• 支援 大樂透、威力彩、539
• 優先用你專案裡的 TaiwanLotteryCrawler;失敗再用官方頁面文字解析(regex)
• 附加 財神方位(若爬不到就帶上友善的 fallback 字樣)

流程
1. try: 自訂 crawler → 成功就拿資料
2. except: → fallback_scrape(kind) 去台彩官網抓字串
3. 丟給 LLM 產出:趨勢重點 + 熱冷號 + 3 組推薦號碼(符合規則)

使用方式
• LINE 訊息輸入:大樂透 / 威力彩 / 539
• 機器人回一則分析與推薦(含注意風險)

  1. 股票查詢:即時快照 + 擴充模組(可選)

亮點
• 識別 2330、00937B、NVDA、^TWII/^GSPC
• 優先用 yfinance 的 fast_info + history,抓名字、現價、漲跌幅
• 若 00937B 等拿不到,fallback 到 YahooStock(如果你專案有)
• 可接你現成模組:stock_price / stock_news / stock_fundamental / stock_dividend
• 整理成一篇 Markdown 報告(技術面/基本面/消息面/建議區間/停利目標)

指令
• 2330、NVDA、台股大盤、美股大盤

  1. TTS/STT:OpenAI ⇄ gTTS、Cloudinary 可選
    • TTS(文字→語音)
    • TTS_PROVIDER=auto:優先 OpenAI tts-1(聲音好),失敗自動換 gTTS(免費)
    • 上傳 Cloudinary 取得公開 URL 回傳 LINE 語音訊息
    • 沒設定 CLOUDINARY_URL 就只回文字(不中斷)
    • STT(語音→文字)
    • 優先用 OpenAI whisper-1
    • 失敗改用 Groq whisper-large-v3
    • 轉文字後再送進人設聊天或分析

實務部署最怕 API key 失效或額度用完,有備援就不會整個功能掛掉。

  1. Groq 模型:改用沒下架的主模

你的日誌明確提示:

The model llama-3.1-70b-versatile has been decommissioned

請改用: llama-3.3-70b-versatile(或你 Console 推薦的最新)
並保留 fallback:llama-3.1-8b-instant(或更換成 3.2/3.3 的 8b 即時模型)。

  1. Render 部署健檢

必填環境變數
• BASE_URL:你的服務 URL(不含 /callback)
• CHANNEL_ACCESS_TOKEN
• CHANNEL_SECRET
• GROQ_API_KEY

選填(有就上,不影響啟動)
• OPENAI_API_KEY
• CLOUDINARY_URL(形如 cloudinary://:@<cloud_name>)
• TTS_PROVIDER:auto / openai / gtts

健康檢查
• 暴露 GET / 與 GET /healthz → 回 200 OK
• 啟動時把 LINE Webhook 指到 BASE_URL/callback(有做兩個端點嘗試)

  1. 使用者指令一覽
    • 主選單:選單 / menu
    • 金融:
    • 金價 / 黃金
    • JPY / USD / EUR
    • 2330、NVDA、台股大盤、美股大盤
    • 彩券:
    • 大樂透、威力彩、539
    • 人設:
    • 甜、鹹、萌、酷、random
    • 翻譯:
    • 翻譯->英文 / 翻譯->日文 / 翻譯->繁體中文
    • 翻譯->結束
    • 群組控制:
    • 開啟自動回答、關閉自動回答(關閉後需 @機器人 才回)

  1. 今日心得:工程的穩定性,來自「可失敗也可活」的設計
    • SDK 不同版本功能不一致,退回穩定實作是最快把線上救回來的方法
    • 爬蟲別迷信 DOM,文字解析+regex 常常更耐改版
    • 外部 API、AI 模型都會變,多一層備援,內心就多一分篤定
    • 雲端服務把健康檢查、Webhook 設定自動化,部署就像呼吸一樣自然

明日預告(Day 23)
• 加上「使用者自訂快捷鍵/巨集指令」
• 新增「一鍵產出查價比較圖表(matplotlib)」
• 佈署前一鍵自測(健康檢查腳本 + webhook 校驗)

TL;DR

https://ithelp.ithome.com.tw/upload/images/20250915/20112100fabW4Ji3oD.png


上一篇
Day21不花錢也能講話:用 gTTS + Cloudinary 讓 her 說出 AI 回覆(含 Groq 模型下架防呆)
下一篇
教育her-重構:用 WebhookParser 取代(不存在的)AsyncWebhookHandler
系列文
雲端情人 - AI 愛25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言